//===============================================================================
// Microsoft patterns & practices
// Web Service Software Factory
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using Microsoft.VisualStudio.Modeling.Shell;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using System.Globalization;
using System.Reflection;

namespace Microsoft.Practices.Modeling.Dsl.Integration.Helpers
{
    /// <summary>
    /// Domain model helper class.
    /// </summary>
    public static class DomainModelHelper
    {
        /// <summary>
        /// Represents the method that loads the domain model of type T from a serialized file.
        /// </summary>
        public delegate T ModelLoader<T>(string modelPath, Store store, SerializationResult result) where T : ModelElement;

        /// <summary>
        /// Gets the surface area.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <returns></returns>
        public static ModelElement GetSurfaceArea(IServiceProvider provider)
        {
            DiagramDocView docView = DesignerHelper.GetModelDocView(provider);

            if(docView != null)
            {
                if(docView.SelectionCount == 1)
                {
                    foreach(object component in docView.GetSelectedComponents())
                    {
                        Diagram diagram = component as Diagram;
                        return diagram;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Gets the selected concept.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <returns></returns>
		public static TConcept GetSelectedConcept<TConcept>(IServiceProvider provider) where TConcept : class
		{
			ModelElement element = GetSelectedElement(provider);

			if(element is TConcept)
			{
				return element as TConcept;
			}

			return default(TConcept);
		}

        /// <summary>
        /// Gets the selected element.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <returns></returns>
        public static ModelElement GetSelectedElement(IServiceProvider provider)
        {
            ShapeElement shape = GetSelectedShape(provider);

            if(shape != null)
            {
                return shape.ModelElement;
            }

            return null;
        }

        /// <summary>
        /// Gets the selected shape.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <returns></returns>
        public static ShapeElement GetSelectedShape(IServiceProvider provider)
        {
            DiagramDocView docView = DesignerHelper.GetModelDocView(provider);

            if(docView != null)
            {
                if(docView.SelectionCount == 1)
                {
                    foreach(object component in docView.GetSelectedComponents())
                    {
                        ShapeElement selectedShape = component as ShapeElement;

                        if(selectedShape != null)
                        {
                            return selectedShape;
                        }
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Gets the selected elements.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <returns></returns>
        public static IList<object> GetSelectedElements(IServiceProvider provider)
        {
            DiagramDocView docView = DesignerHelper.GetModelDocView(provider);

            if(docView != null)
            {
                if(docView.SelectionCount > 0)
                {
                    IList<object> elements = new List<object>(docView.SelectionCount);

                    foreach(object component in docView.GetSelectedComponents())
                    {
                        ShapeElement selectedShape = component as ShapeElement;

                        if(selectedShape != null)
                        {
                            elements.Add(selectedShape.ModelElement);
                        }
                    }

                    return elements;
                }
            }

            return null;
        }

        /// <summary>
        /// Gets the element.
        /// </summary>
        /// <param name="store">The store.</param>
        /// <returns></returns>
        public static T GetElement<T>(Store store) where T : ModelElement
        {
            T element = default(T);
            foreach (ModelElement mel in store.ElementDirectory.AllElements)
            {
                if (mel is T)
                {
                    element = (T)mel;
                    break;
                }
            }
            return element;
        }

        /// <summary>
        /// Gets the element.
        /// </summary>
        /// <param name="store">The store.</param>
        /// <param name="elementId">The element id.</param>
        /// <returns></returns>
        public static T GetElement<T>(Store store, Guid elementId) where T : ModelElement
        {
            return (T)store.ElementDirectory.GetElement(elementId);
        }

        /// <summary>
        /// Loads the domain model serialized in the specified path.
        /// </summary>
        /// <param name="modelPath">The model path.</param>
        /// <param name="loader">The model loader.</param>
        /// <returns></returns>
        public static TModel LoadModel<TDomainModel, TModel>(string modelPath, ModelLoader<TModel> loader)
            where TDomainModel : DomainModel
            where TModel : ModelElement
        {
            Store store = new Store(typeof(CoreDesignSurfaceDomainModel), typeof(TDomainModel));
            SerializationResult result = new SerializationResult();

            using (Transaction transaction = store.TransactionManager.BeginTransaction("Load", true))
            {
                TModel model = loader(modelPath, store, result);
                if (result.Failed)
                {
                    throw new SerializationException(result);
                }
                transaction.Commit();
                return model;
            }
        }

		/// <summary>
		/// Add/update the linked element and remove any duplicate.
		/// </summary>
		/// <param name="collection">The collection.</param>
		/// <param name="element">The element.</param>
		/// <param name="renameOnExisting">if set to <c>true</c> [rename on existing].</param>
		public static void AddLinkedElement<T>(LinkedElementCollection<T> collection, T element)
			where T : ModelElement
		{
			RemoveDuplicateElement(collection, element);
			collection.Add(element);
		}

		/// <summary>
		/// Removes the duplicate element.
		/// </summary>
		/// <param name="collection">The collection.</param>
		/// <param name="element">The element.</param>
		/// <returns>True if a duplicate element was removed, False otherwise.</returns>
		public static bool RemoveDuplicateElement<T>(LinkedElementCollection<T> collection, T element)
			where T : ModelElement
		{
			T found = null;
			string name = GetNameProperty(element);
			// try finding a duplicate element (same name and != Id)
			if (!string.IsNullOrEmpty(name))
			{
				found = collection.Find(delegate(T match)
				{
					return name.Equals(GetNameProperty(match), StringComparison.InvariantCultureIgnoreCase); //&& match.Id != element.Id;
				});
			}

			if (found != null)
			{
				// Remove the duplicate
				collection.Remove(found);
				return true;
			}

			return false;
		}

		private static string GetNameProperty(ModelElement element)
		{
			PropertyInfo property = element.GetType().GetProperty("Name");
			if (property == null)
			{
				return null;
			}
			return property.GetValue(element, null) as string;
		}
    }
}
